swift_too
moduleSwift_Clock
example - handling corrections to Swift's reported timesswifttools
= 2.4from swifttools.swift_too import Clock, ObsQuery, VisQuery
from datetime import datetime,timedelta
Swift has it's own built in clock, and it is this clock that timestamps all Swift events, including telemetry points, X-ray photon event times, the start and stop of observations and the like. This clock measures time in what's know as Mission Elapse Time (MET). This is defined simply as the number of seconds since January 1st, 2001, as measured by Swift's clock.
In addition, we have a time system known as "Swift Time", this is simply a conversion of MET into a standard UT time format, so for example, for MET = 600000000s, 600 million seconds after Jan 1st, 2001, let's calculate what that would be in "Swift Time".
met = 600000000
print(f"MET {met} = {datetime(2001,1,1) + timedelta(seconds=met)}")
MET 600000000 = 2020-01-06 10:40:00
As you can see, 600 million seconds is approximately 19 years.
"Swift Time" is approximately UTC, however, there are several issues that mean that it is not actually UTC.
Firstly, it does not include any leap seconds. There have been 5 leap seconds, most recently at the end of 2016 (as of writing), added to the UTC scale since the epoch of MET, Jan 1st, 2001. You can find out information about leap seconds at this NIST page.
Secondly, and more crucially, as Swift's clock is fully internal, and not corrected by GPS and otherwise, and it unfortunately is drifting over time. At Swift entered it's 17th year of operations in November 2021, this drift although small has become significant, and needs to be corrected for if you wish to obtain times that accurately match UT time.
For this reason we have the concept of the Universal Time Correction Factor (UTCF). This is a floating point value that when added to Swift Time, corrects it to UTC. The UTCF includes both a correction for leap seconds, and the clock drift. As of Jan 1st, 2022, the utcf value is -28.3567 seconds, including 5 leap seconds. Therefore to correct Swift Time to UTC, on Jan 1st, 2022, add this value.
However, this clock correction changes non-linearly with time, therefore the correction needs to be calculated for each time being corrected.
The Swift_Clock
class is designed to convert the three time formats: MET (met
), Swift Time (swifttime
) and UTC Time (utctime
), by applying the UTCF (utcf
). The class is fairly simple, simply give one of the three time formats, and it will return back the other two, and the UTCF value. Here we demonstrate it's usage:
cc = Clock(swifttime='2022-01-01 00:00:00')
cc
MET (s) | Swift Time (default) | UTC Time | UTCF (s) |
---|---|---|---|
662688000.0 | 2022-01-01 00:00:00 | 2021-12-31 23:59:31.643287 | -28.356713 |
So we can see by giving a single time, here in the Swift Time format, the Swift_Clock
class returns 4 values, met
the Mission elapsed time in seconds, the echoed back Swift Time, UTC time, and UTCF. First let's take a look at the UTC time, here represented by the utctime
attribute.
print(cc.utctime)
2021-12-31 23:59:31.643287
So this is a representation of the actual UTC time when the Swift clock clicked over to 2022. Think of it this way, Swift set off the fireworks to celebrate the New Year 28.3567 seconds to early, because it's clock is fast by 23.3567 seconds, and it doesn't know anything about the 5 leap seconds since the start of 2001. Note that utctime
is a datetime instance. To keep things simple, it is a "naive datetime", i.e. it does not have a timezone attached.
datetime
moduleA note about the Python datetime
module. It does not support leap seconds, therefore although it's useful to use it to represent UTC time with, as it provides an easy container format, if you use it to do arithmatic between dates, it will not give the correct value.
Let's demonstrate this by looking at how datetime
handles a know leap second, which occurred at the end of 2016.
dt1 = datetime(2016,12,31,23,59,59)
dt2 = datetime(2017,1,1,0,0,0)
dttd = dt2 - dt1
print(f"datetime time difference = {dttd.total_seconds():.1f}s")
datetime time difference = 1.0s
So according to datetime, between 23:59:59 and midnight, there was one second, but we know there was a leap second here.
If you want to handle these leap seconds correctly, I would recommend using astropy
Time
module. Let's see what happens when we use this. Thankfully Time
can easily convert datetime
values into UTC values. Let's do the above again, but using Time
.
from astropy.time import Time
import astropy.units as u
at1 = Time(dt1,format='datetime',scale='utc')
at2 = Time(dt2,format='datetime',scale='utc')
atd = at2 - at1
print(f"Time difference = {atd.to_value(u.s):.1f}s")
Time difference = 2.0s
The value here is 2 seconds because there was a leap second at the end of 2016, meaning we here was a time of 23:59:60 that was the actual last second of 2016. astropy
handles this, but datetime
does not. Therefore if strict accuracy is needed for calculations over large time periods, I would recommend converting to astropy
Time
.
Converting single values is useful, for example, if you know a GRB went off a certain time, and you have BAT data you wish to compare too, BAT events are tagged with MET, Swift_Clock
allows for easy conversion of a UTC time to a MET.
However, in many cases you may wish to correct a larger range of numbers. This is simple to do, you can simply pass them as alist
or tuple
. For example:
cc = Clock()
cc.utctime = '2022-01-01 00:00:00', '2021-01-01 00:00:00', '2020-01-01 00:00:00'
cc.submit()
cc
MET (s) | Swift Time | UTC Time (default) | UTCF (s) |
---|---|---|---|
662688028.356715 | 2022-01-01 00:00:28.356715 | 2022-01-01 00:00:00 | -28.356715 |
631152026.251282 | 2021-01-01 00:00:26.251282 | 2021-01-01 00:00:00 | -26.251282 |
599529624.18474 | 2020-01-01 00:00:24.184740 | 2020-01-01 00:00:00 | -24.18474 |
Here you see that the UTCF values are different for each time, as the three times given are a year apart. Note that the drift seems to increasing by approximately 2.1 seconds every year.
You can access the arrays of METs, Swift Times and UTC times using the met
, swifttime
and utctime
parameters as expected, and they will return a list:
cc.met
[662688028.356715, 631152026.251282, 599529624.18474]
However, you can also access times directly as follows:
cc[0]
MET (s) | Swift Time | UTC Time (default) | UTCF (s) |
---|---|---|---|
662688028.356715 | 2022-01-01 00:00:28.356715 | 2022-01-01 00:00:00 | -28.356715 |
swiftdatetime
cc[0]
returns swiftdatetime
object. swiftdatetime
is an extended version of the standard python datetime
class. It is extended inso far that it has new attributes, met
, utctime
, swifttime
and isutc
.
isutc
determines what time system it is defined in. Above you see that it says "UTC Time (default)" which means that isutc = True
. If we print it, it will give us the UTC time by default:
print(cc[0])
2022-01-01 00:00:00
swiftdatetime
objects work exactly like datetime
objects, you can add to them, subtract them from one another, and use standard methods to create them. The following are some examples. Firstly if we subtract one from another, we get a timedelta
object.
print(f"{cc[0]} - {cc[1]} = {cc[0]-cc[1]}")
2022-01-01 00:00:00 - 2021-01-01 00:00:00 = 365 days, 0:00:00
Note that we can add time to swiftdatetime
instances also. But it's important to note that the resultant value will not include a UTCF value. This is because UTCF changes non-linearly with time, so cannot be propogated. So the end result will be a swiftdatetime
in time system what's being added to:
one_week_after = cc[0] + timedelta(days=7)
one_week_after
MET (s) | Swift Time | UTC Time (default) | UTCF (s) |
---|---|---|---|
None | None | 2022-01-08 00:00:00 | None |
Note that only utctime
is set for this new value.
If we wish to obtain UTCF and therefore Swift Time and MET for this result, we will need to look up the UTCF with Swift_Clock
.
Clock(utctime=one_week_after)
MET (s) | Swift Time | UTC Time (default) | UTCF (s) |
---|---|---|---|
663292828.397507 | 2022-01-08 00:00:28.397507 | 2022-01-08 00:00:00 | -28.397507 |
Not that here, as one_week_after
is in the UTC time system, we need to assign it to utctime. If we tried to assign it to swifttime
it would cause an error, because one_week_after.swifttime == None
.
Using the new clock_correct
method, any class that returns times as a result can have those values clock corrected. Let's demonstrate that with an ObsQuery
.
obs = ObsQuery(name='RR Lyrae')
obs
Begin Time | End Time | Target Name | Observation Number | Exposure (s) | Slewtime (s) |
---|---|---|---|---|---|
2013-01-18 14:22:02 | 2013-01-18 14:27:01 | Kepler_Reg3_Pt03 | 00048634001 | 235 | 64 |
2013-01-31 22:32:34 | 2013-01-31 22:45:58 | Kepler_Reg3_Pt03 | 00048634002 | 650 | 154 |
2013-02-06 08:30:02 | 2013-02-06 08:39:58 | Kepler_Reg3_Pt03 | 00048634003 | 510 | 86 |
2013-02-06 14:44:02 | 2013-02-06 14:50:57 | Kepler_Reg3_Pt03 | 00048634003 | 255 | 160 |
2013-02-06 19:46:02 | 2013-02-06 19:53:01 | Kepler_Reg3_Pt03 | 00048634003 | 230 | 189 |
Here the 5 Swift observations of RR Lyrae, have begin and end times associated with them, if we look at those times now, we will find that they are now swiftdatetime
instances, which like the output of Swift_Clock
, contain information about MET, UTC Time and UTCF as well as Swift Time.
obs[0].begin
datetime.datetime(2013, 1, 18, 14, 22, 2)
However, in this case you see that the default time is Swift Time, and there is no UTCF defined. Therefore we do not know what the UTC time for the start of the first observation is. However, there is a new method to add these corrections:
obs.clock_correct()
obs
Begin Time (UTC) | End Time (UTC) | Target Name | Observation Number | Exposure (s) | Slewtime (s) |
---|---|---|---|---|---|
2013-01-18 14:21:51.884888 | 2013-01-18 14:26:50.884875 | Kepler_Reg3_Pt03 | 00048634001 | 234 | 63 |
2013-01-31 22:32:23.834881 | 2013-01-31 22:45:47.834847 | Kepler_Reg3_Pt03 | 00048634002 | 649 | 153 |
2013-02-06 08:29:51.814584 | 2013-02-06 08:39:47.814558 | Kepler_Reg3_Pt03 | 00048634003 | 509 | 85 |
2013-02-06 14:43:51.813610 | 2013-02-06 14:50:46.813592 | Kepler_Reg3_Pt03 | 00048634003 | 254 | 159 |
2013-02-06 19:45:51.812824 | 2013-02-06 19:52:50.812806 | Kepler_Reg3_Pt03 | 00048634003 | 229 | 188 |
Note that the table the times have now changed, and are labelled as being in UTC time. However, the information about Swift Time, UTCF and MET are retained, as we can see if we look at one of time times individually.
obs[0].begin
MET (s) | Swift Time | UTC Time (default) | UTCF (s) |
---|---|---|---|
380211722.0 | 2013-01-18 14:22:02 | 2013-01-18 14:21:51.884888 | -10.115112 |
So, if you want to switch the default time for back Swift Time, you can easily do this. In fact you can swap between UTC and Swift Time using the to_utctime()
and to_swifttime()
methods. So if we want to convert all of the time in our ObsQuery
to back to Swift Time for display as a table, just do the following:
obs.to_swifttime()
obs
Begin Time (Swift) | End Time (Swift) | Target Name | Observation Number | Exposure (s) | Slewtime (s) |
---|---|---|---|---|---|
2013-01-18 14:22:02 | 2013-01-18 14:27:01 | Kepler_Reg3_Pt03 | 00048634001 | 235 | 64 |
2013-01-31 22:32:34 | 2013-01-31 22:45:58 | Kepler_Reg3_Pt03 | 00048634002 | 650 | 154 |
2013-02-06 08:30:02 | 2013-02-06 08:39:58 | Kepler_Reg3_Pt03 | 00048634003 | 510 | 86 |
2013-02-06 14:44:02 | 2013-02-06 14:50:57 | Kepler_Reg3_Pt03 | 00048634003 | 255 | 160 |
2013-02-06 19:46:02 | 2013-02-06 19:53:01 | Kepler_Reg3_Pt03 | 00048634003 | 230 | 189 |
So now the observation table times are displayed as UTC times, rather than Swift Times.
So as default, anything relating to looking at Swift prior observations and Swift Planned observations are returned in the Swift time system. Therefore the default time system that Swift_ObsQuery
and Swift_PlanQuery
work in is Swift Time.
For Swift_VisQuery
which queries when targets are visibile to Swift, as this information is calculated in UTC time system by default. Let's see this in action, by performing a Swift_VisQuery
.
vis = VisQuery(266,-29,hires=True,length=1)
vis
Begin Time | End Time | Window length |
---|---|---|
2022-03-28 19:58:00 | 2022-03-28 20:26:00 | 0:28:00 |
2022-03-28 21:23:00 | 2022-03-28 22:02:00 | 0:39:00 |
2022-03-28 22:58:00 | 2022-03-28 23:37:00 | 0:39:00 |
2022-03-29 00:34:00 | 2022-03-29 01:13:00 | 0:39:00 |
2022-03-29 02:09:00 | 2022-03-29 02:48:00 | 0:39:00 |
2022-03-29 03:45:00 | 2022-03-29 04:24:00 | 0:39:00 |
2022-03-29 05:21:00 | 2022-03-29 05:59:00 | 0:38:00 |
2022-03-29 06:56:00 | 2022-03-29 07:35:00 | 0:39:00 |
2022-03-29 08:32:00 | 2022-03-29 09:11:00 | 0:39:00 |
2022-03-29 10:07:00 | 2022-03-29 10:46:00 | 0:39:00 |
2022-03-29 11:43:00 | 2022-03-29 12:15:00 | 0:32:00 |
2022-03-29 13:18:00 | 2022-03-29 13:54:00 | 0:36:00 |
2022-03-29 14:54:00 | 2022-03-29 15:32:00 | 0:38:00 |
2022-03-29 16:29:00 | 2022-03-29 17:08:00 | 0:39:00 |
2022-03-29 18:05:00 | 2022-03-29 18:44:00 | 0:39:00 |
2022-03-29 19:41:00 | 2022-03-29 19:58:00 | 0:17:00 |
OK now let's perform a clock correction on this.
vis.clock_correct()
vis
Begin Time (UTC) | End Time (UTC) | Window length |
---|---|---|
2022-03-28 19:58:00 | 2022-03-28 20:26:00 | 0:28:00 |
2022-03-28 21:23:00 | 2022-03-28 22:02:00 | 0:39:00 |
2022-03-28 22:58:00 | 2022-03-28 23:37:00 | 0:39:00 |
2022-03-29 00:34:00 | 2022-03-29 01:13:00 | 0:39:00 |
2022-03-29 02:09:00 | 2022-03-29 02:48:00 | 0:39:00 |
2022-03-29 03:45:00 | 2022-03-29 04:24:00 | 0:39:00 |
2022-03-29 05:21:00 | 2022-03-29 05:59:00 | 0:38:00 |
2022-03-29 06:56:00 | 2022-03-29 07:35:00 | 0:39:00 |
2022-03-29 08:32:00 | 2022-03-29 09:11:00 | 0:39:00 |
2022-03-29 10:07:00 | 2022-03-29 10:46:00 | 0:39:00 |
2022-03-29 11:43:00 | 2022-03-29 12:15:00 | 0:32:00 |
2022-03-29 13:18:00 | 2022-03-29 13:54:00 | 0:36:00 |
2022-03-29 14:54:00 | 2022-03-29 15:32:00 | 0:38:00 |
2022-03-29 16:28:59.999999 | 2022-03-29 17:08:00 | 0:39:00.000001 |
2022-03-29 18:05:00 | 2022-03-29 18:44:00 | 0:39:00 |
2022-03-29 19:41:00 | 2022-03-29 19:58:00 | 0:17:00 |
OK so now we see that the Begin and End times are labelled as UTC, but they are unchanged from before as they were always calculated in UTC. This is because the API knows that VisQuery operates in UTC time system. You can of course Switch to Swift times using to_swifttime()
method.
vis.to_swifttime()
vis
Begin Time (Swift) | End Time (Swift) | Window length |
---|---|---|
2022-03-28 19:58:28.863409 | 2022-03-28 20:26:28.863523 | 0:28:00.000114 |
2022-03-28 21:23:28.863754 | 2022-03-28 22:02:28.863912 | 0:39:00.000158 |
2022-03-28 22:58:28.864140 | 2022-03-28 23:37:28.864298 | 0:39:00.000158 |
2022-03-29 00:34:28.864529 | 2022-03-29 01:13:28.864687 | 0:39:00.000158 |
2022-03-29 02:09:28.864914 | 2022-03-29 02:48:28.865072 | 0:39:00.000158 |
2022-03-29 03:45:28.865304 | 2022-03-29 04:24:28.865462 | 0:39:00.000158 |
2022-03-29 05:21:28.865693 | 2022-03-29 05:59:28.865847 | 0:38:00.000154 |
2022-03-29 06:56:28.866078 | 2022-03-29 07:35:28.866237 | 0:39:00.000159 |
2022-03-29 08:32:28.866468 | 2022-03-29 09:11:28.866626 | 0:39:00.000158 |
2022-03-29 10:07:28.866853 | 2022-03-29 10:46:28.867011 | 0:39:00.000158 |
2022-03-29 11:43:28.867242 | 2022-03-29 12:15:28.867372 | 0:32:00.000130 |
2022-03-29 13:18:28.867628 | 2022-03-29 13:54:28.867774 | 0:36:00.000146 |
2022-03-29 14:54:28.868017 | 2022-03-29 15:32:28.868171 | 0:38:00.000154 |
2022-03-29 16:29:28.868402 | 2022-03-29 17:08:28.868561 | 0:39:00.000159 |
2022-03-29 18:05:28.868792 | 2022-03-29 18:44:28.868950 | 0:39:00.000158 |
2022-03-29 19:41:28.869181 | 2022-03-29 19:58:28.869250 | 0:17:00.000069 |